{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9prMxR0UQQsH"
      },
      "source": [
        "# Command: A new tool for building multi-agent architectures in LangGraph\n",
        "\n",
        "\n",
        "Building agentic and multi-agent systems is all about communication.\n",
        "\n",
        "Now, nodes can dynamically decide which node to execute next, improving flexibility and simplifying complex workflows.\n",
        "\n",
        "#### **What's New:**\n",
        "- **Edgeless graphs:** Nodes no longer need edges to connect. Instead, they can directly specify which node to go to next, making your agent flows more intuitive and adaptable.\n",
        "\n",
        "- **Enhanced control:** Control the flow after the state update by returning a Command that points to the next node, enabling dynamic, real-time decision-making."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "WreMXY-QQQdY"
      },
      "outputs": [],
      "source": [
        "%%capture --no-stderr\n",
        "%pip install -U langgraph"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "I0VX1uLTVYxr"
      },
      "source": [
        "Let's learn it with a simple example - An\n",
        "AI-powered home search engine like: https://flyhomes.com/.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {
        "id": "iGlUpK3eTQ1Y"
      },
      "outputs": [],
      "source": [
        "import random\n",
        "from typing import Annotated, TypedDict, Literal, Optional\n",
        "\n",
        "from langgraph.graph import StateGraph, START\n",
        "from langgraph.graph.message import add_messages\n",
        "from langchain_core.messages import HumanMessage, AIMessage\n",
        "\n",
        "# This is new\n",
        "from langgraph.types import Command"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cpXGrqZrVu5O"
      },
      "source": [
        "## S1: Understand Command Basic Usage"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "metadata": {
        "id": "aQh6dGpHXGTy"
      },
      "outputs": [],
      "source": [
        "# Define graph state\n",
        "class State(TypedDict):\n",
        "    address: str # address where user wants to search homes for.\n",
        "    nearby_homes: Optional[list[dict]] # list of nearby homes\n",
        "    messages: Annotated[list, add_messages]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "metadata": {
        "id": "Sn7rSdQ_SU7A"
      },
      "outputs": [],
      "source": [
        "# Define the nodes (agents)\n",
        "\n",
        "def node_a(state: State) -> Command[Literal[\"search_nearby_homes\", \"answer_question\"]]:\n",
        "    print(\"Called A\")\n",
        "\n",
        "    # THis is where llm decides which node shall I go next\n",
        "    value = random.choice([\"search_nearby_homes\", \"answer_question\"])\n",
        "\n",
        "    # This is a replacement for a conditional edge function\n",
        "    if value == \"search_nearby_homes\":\n",
        "        goto = \"search_nearby_homes\"\n",
        "    else:\n",
        "        goto = \"answer_question\"\n",
        "\n",
        "    # note how Command allows you to BOTH update the graph state AND route to the next node\n",
        "    return Command(\n",
        "        # this is a replacement for an edge\n",
        "        goto=goto,\n",
        "    )\n",
        "\n",
        "\n",
        "# Nodes search_nearby_homes and answer_question are unchanged (just like before)\n",
        "def search_nearby_homes(state: State):\n",
        "    print(\"Called search_nearby_homes!\")\n",
        "    return {\"nearby_homes\": [{\"home_1\": \"Zia House\"}, {\"home_2\": \"Qasim House\"}]}\n",
        "\n",
        "\n",
        "def answer_question(state: State):\n",
        "    print(\"Called answer_question\")\n",
        "    return {\"messages\": [AIMessage(content=\"System is down - please try later\")]}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "metadata": {
        "id": "RcmNMkI-VIOI"
      },
      "outputs": [],
      "source": [
        "builder = StateGraph(State)\n",
        "builder.add_edge(START, \"node_a\")\n",
        "builder.add_node(node_a)\n",
        "builder.add_node(search_nearby_homes)\n",
        "builder.add_node(answer_question)\n",
        "# NOTE: there are no edges between nodes A, B and C!\n",
        "\n",
        "graph = builder.compile()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 266
        },
        "id": "jsyM2Z6dVMgi",
        "outputId": "d35f250d-e4b5-4654-a647-d75754968646"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from IPython.display import display, Image\n",
        "\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "y13nIFUmVO0O",
        "outputId": "bd470936-3b67-4b18-8ed0-586ec2350fd9"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Called A\n",
            "Called answer_question\n",
            "\n",
            "[FINAL RESPONSE]\n",
            " {'address': 'Karachi', 'messages': [AIMessage(content='System is down - please try later', additional_kwargs={}, response_metadata={}, id='fe470f7e-fa75-4b20-b584-b116735ad8c0')]}\n"
          ]
        }
      ],
      "source": [
        "agent_response = graph.invoke({\"address\": \"Karachi\"})\n",
        "\n",
        "print(\"\\n[FINAL RESPONSE]\\n\", agent_response)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1NE-PRe2WwAz"
      },
      "source": [
        "## S2: Update State using Command\n",
        "\n",
        "After node_1 we can update our state as well. So if user is already in our system we can add it's user data in the stage to personalize user experience."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "metadata": {
        "id": "KgMwEzaTXL0O"
      },
      "outputs": [],
      "source": [
        "# Define graph state\n",
        "class State(TypedDict):\n",
        "    address: str # address where user wants to search homes for.\n",
        "    nearby_homes: Optional[list[dict]] # list of nearby homes\n",
        "    messages: Annotated[list, add_messages]\n",
        "    user_data: Optional[dict] # user data - for registered users we already have this"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "metadata": {
        "id": "WlV_2nMqXA88"
      },
      "outputs": [],
      "source": [
        "# Define the nodes (agents)\n",
        "\n",
        "def node_a(state: State) -> Command[Literal[\"search_nearby_homes\", \"answer_question\"]]:\n",
        "    print(\"Called A\")\n",
        "\n",
        "    # Get User Data From Memory or Data Store\n",
        "    fetched_user_data = {\"name\": \"Ammen Alam\"}\n",
        "\n",
        "    # THis is where llm decides which node shall I go next\n",
        "    value = random.choice([\"search_nearby_homes\", \"answer_question\"])\n",
        "\n",
        "    # This is a replacement for a conditional edge function\n",
        "    if value == \"search_nearby_homes\":\n",
        "        goto = \"search_nearby_homes\"\n",
        "    else:\n",
        "        goto = \"answer_question\"\n",
        "\n",
        "    # note how Command allows you to BOTH update the graph state AND route to the next node\n",
        "    return Command(\n",
        "        update={\"user_data\": fetched_user_data},\n",
        "        # this is a replacement for an edge\n",
        "        goto=goto,\n",
        "    )\n",
        "\n",
        "\n",
        "# Nodes search_nearby_homes and answer_question are unchanged (just like before)\n",
        "def search_nearby_homes(state: State):\n",
        "    print(\"Called search_nearby_homes!\")\n",
        "    print(\"UserInfo\", state[\"user_data\"])\n",
        "    return {\"nearby_homes\": [{\"home_1\": \"Zia House\"}, {\"home_2\": \"Qasim House\"}]}\n",
        "\n",
        "\n",
        "def answer_question(state: State):\n",
        "    print(\"Called answer_question\")\n",
        "    print(\"UserInfo\", state[\"user_data\"])\n",
        "    user_name = state[\"user_data\"].get(\"name\", \"Guest\")\n",
        "    return {\"messages\": [AIMessage(content=f\"Hi, {user_name} Welcome to Homes AI Search Engine\")]}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 40,
      "metadata": {
        "id": "0Zu-DQrCYH-x"
      },
      "outputs": [],
      "source": [
        "builder = StateGraph(State)\n",
        "builder.add_edge(START, \"node_a\")\n",
        "builder.add_node(node_a)\n",
        "builder.add_node(search_nearby_homes)\n",
        "builder.add_node(answer_question)\n",
        "# NOTE: there are no edges between nodes A, B and C!\n",
        "\n",
        "graph = builder.compile()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 41,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 266
        },
        "id": "tmWKK6wvYKMC",
        "outputId": "da188a3b-7d4c-4be6-9803-ecfce72c3135"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from IPython.display import display, Image\n",
        "\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 43,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "VnvpB566YEy2",
        "outputId": "c4f87f2b-c567-41a0-a305-5c431a11cf8d"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Called A\n",
            "Called answer_question\n",
            "UserInfo {'name': 'Ammen Alam'}\n",
            "\n",
            "[FINAL RESPONSE]\n",
            " {'address': 'Karachi', 'messages': [AIMessage(content='Hi, Ammen Alam Welcome to Homes AI Search Engine', additional_kwargs={}, response_metadata={}, id='28196ff3-8ccb-4e92-850f-07c298251285')], 'user_data': {'name': 'Ammen Alam'}}\n"
          ]
        }
      ],
      "source": [
        "agent_response = graph.invoke({\"address\": \"Karachi\"})\n",
        "\n",
        "print(\"\\n[FINAL RESPONSE]\\n\", agent_response)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7iuYLfWZV4E-"
      },
      "source": [
        "# S3: Run Nodes(Agents) in Parallel\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XD5NPfzzWYDP"
      },
      "source": [
        "Now our user requirement is to both find homes and have conversation with user.\n",
        "For this:\n",
        "\n",
        "- We shall call Both Nodes in Parrallel"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 44,
      "metadata": {
        "id": "-yLGnl9XYY7g"
      },
      "outputs": [],
      "source": [
        "# Define graph state\n",
        "class State(TypedDict):\n",
        "    address: str # address where user wants to search homes for.\n",
        "    nearby_homes: Optional[list[dict]] # list of nearby homes\n",
        "    messages: Annotated[list, add_messages]\n",
        "    user_data: Optional[dict] # user data - for registered users we already have this"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 47,
      "metadata": {
        "id": "YhzGbxG8WVFZ"
      },
      "outputs": [],
      "source": [
        "# Define the nodes (agents)\n",
        "\n",
        "def node_a(state: State) -> Command[Literal[\"search_nearby_homes\", \"answer_question\"]]:\n",
        "    print(\"Called A\")\n",
        "\n",
        "    # Get User Data From Memory or Data Store\n",
        "    fetched_user_data = {\"name\": \"Ammen Alam\"}\n",
        "\n",
        "    # note how Command allows you to BOTH update the graph state AND route to the next node\n",
        "    return Command(\n",
        "        update={\"user_data\": fetched_user_data},\n",
        "        # this is a replacement for an edge\n",
        "        goto=[\"search_nearby_homes\", \"answer_question\"],\n",
        "    )\n",
        "\n",
        "\n",
        "# Nodes search_nearby_homes and answer_question are unchanged (just like before)\n",
        "def search_nearby_homes(state: State):\n",
        "    print(\"Called search_nearby_homes!\")\n",
        "    print(\"UserInfo\", state[\"user_data\"])\n",
        "    return {\"nearby_homes\": [{\"home_1\": \"Zia House\"}, {\"home_2\": \"Qasim House\"}]}\n",
        "\n",
        "\n",
        "def answer_question(state: State):\n",
        "    print(\"Called answer_question\")\n",
        "    print(\"UserInfo\", state[\"user_data\"])\n",
        "    user_name = state[\"user_data\"].get(\"name\", \"Guest\")\n",
        "    return {\"messages\": [AIMessage(content=f\"Hi, {user_name} Welcome to Homes AI Search Engine\")]}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 48,
      "metadata": {
        "id": "qj3qPdcfYdj0"
      },
      "outputs": [],
      "source": [
        "builder = StateGraph(State)\n",
        "builder.add_edge(START, \"node_a\")\n",
        "builder.add_node(node_a)\n",
        "builder.add_node(search_nearby_homes)\n",
        "builder.add_node(answer_question)\n",
        "# NOTE: there are no edges between nodes A, B and C!\n",
        "\n",
        "graph = builder.compile()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 49,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "KST9z3eMYkwH",
        "outputId": "b257facf-426d-4f38-f1da-91ffd6f4accf"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Called A\n",
            "Called search_nearby_homes!\n",
            "UserInfo {'name': 'Ammen Alam'}\n",
            "Called answer_question\n",
            "UserInfo {'name': 'Ammen Alam'}\n",
            "\n",
            "[FINAL RESPONSE]\n",
            " {'address': 'Karachi', 'nearby_homes': [{'home_1': 'Zia House'}, {'home_2': 'Qasim House'}], 'messages': [AIMessage(content='Hi, Ammen Alam Welcome to Homes AI Search Engine', additional_kwargs={}, response_metadata={}, id='0d234fef-d86b-4d48-8a79-b1347b91ade3')], 'user_data': {'name': 'Ammen Alam'}}\n"
          ]
        }
      ],
      "source": [
        "agent_response = graph.invoke({\"address\": \"Karachi\"})\n",
        "\n",
        "print(\"\\n[FINAL RESPONSE]\\n\", agent_response)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e2BHrLUbYpXZ"
      },
      "source": [
        "##"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BnyPOlOaY5zz"
      },
      "source": [
        "## Final Notes"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uDbJ-HMdZBri"
      },
      "source": [
        "\n",
        "- **Task**: In `node_a` replace that random with an llm and it will decide in which node to go next.\n",
        "\n",
        "- **Question:** What will happen if two nodes running in parallel try update same key in state (Think about reducers and find your answer in Module 4)\n",
        "\n",
        "References:\n",
        "- https://langchain-ai.github.io/langgraph/how-tos/command/#define-graph\n",
        "- https://blog.langchain.dev/command-a-new-tool-for-multi-agent-architectures-in-langgraph/"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "j26dblOFY9fo"
      },
      "outputs": [],
      "source": []
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "cpXGrqZrVu5O"
      ],
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
